/**
 * Testing build
 * Refer https://wiki.cordys.com/display/Vanenburgapp/Auto+suggestion+API+for+CPF+form+fields for details on
 * usage
 */
if(!window["vg"]) {
	vg = {};
}

if(!vg.visualization) {
	vg.visualization = {};
}

vg.visualization.AutoCompleteMenu = (function () {	
	function AutoComplete(inputObj) {
		this.isGrid = inputObj.isGrid || false;
		if(this.isGrid) {
			if(!(inputObj.tableObject || inputObj.columnNumber)) {
				throw "tableObject or columnNumber property missing in the input object";
			}
			this.tableObject = inputObj.tableObject;
			var model = window[this.tableObject.model];
			var insertHandler = bindEvent(this._insertHandler, this);
			model.addListener("xforms-onafterinsert",insertHandler);
			var deleteRefreshHandler = bindEvent(this._deleteAndRefreshHandler, this);
			model.addListener("xforms-onafterdelete", deleteRefreshHandler);
			model.addListener("xforms-onafterrefresh", deleteRefreshHandler);
			this.columnNumber = inputObj.columnNumber;
		}
		this.wsModel = inputObj.model;
		this.menuHeight = addPx(inputObj.height);
		this.menuWidth = addPx(inputObj.width);	
		this.menuTop = addPx(inputObj.top);
		this.menuLeft = addPx(inputObj.left);
		this.inputFldId = this.isGrid ? this.columnNumber : inputObj.inputFldId;
		this.menuDivId = this.inputFldId + "_menu_div";
		this.wsInputObj = this._cacheInputElements(inputObj.wsInputObj);
		this.includeLeadingSpaces = !!(inputObj.includeLeadingSpaces);
		this.includeTrailingSpaces = !!(inputObj.includeTrailingSpaces);
		this.searchRegEx = inputObj.searchExpression;
		this.entryConfigArray = inputObj.entryConfigArray; 
		this.fieldFillValue = inputObj.fieldFillValue;
		this.singleFire = inputObj.singleFire || false;
		this.wsFired = false;
		this.selectionHandler = null;
		this.cacheObj = {};
		this._setKeyEvents();
		this.timeoutId = null;
		this.previousSearchText = "";
	};
	
	AutoComplete.prototype = (function () {
		
		/**
		 * For each object entry in the array, caches the HTML input element corresponding to the id mentioned in the
		 * fldId property of the object. If the object has the isColumn property true, then the fldId is considered as
		 * the column number of the table denoted by tableObject. Then the input elements in column are cached.
		 * @param {Object} obj - if called without obj, wsInputObj is taken by default
		 */
		var _cacheInputElements = function (arr) {
			arr = arr || this.wsInputObj;
			for(var i =0; i < arr.length; i++) {
				if(this.isGrid && arr[i].isColumn) {
					arr[i].element = this._cacheFullColumns(arr[i].fldId);
				} else {
					arr[i].element = window[arr[i].fldId];
				}
			}
			return arr;
		}
		
		/**
		 * To cache the input elements in a column from the table object
		 * @param {Object} columnNumber - number of the column taking the leftmost column as number 1
		 * @param {Object} keyPressHandler - if provided with a function, it will be attached to the onkeyup and onkeydown 
		 * events of the elements
		 */
		var _cacheFullColumns = function (columnNumber, keyPressHandler) {
			var rows = this.tableObject.getRows();
			var cacheArray = [null];
			for (var i = 0; i < rows.length; i++) {
				this._cacheSingleColumn(columnNumber, cacheArray, keyPressHandler, rows[i]);
			}
			return cacheArray;
		}
		
		/**
		 * To cache a column of a particular row in the table object
		 * @param {Object} columnNumber - Number of the column to be cached
		 * @param {Object} cacheArray - The array to which the element has to be cached
		 * @param {Object} keyPressHandler - Keypress event handler if required
		 * @param {Object} row - If provided then the column will be taken from this row. Else the column will be
		 * taken from the currently active row
		 */
		var _cacheSingleColumn = function (columnNumber, cacheArray, keyPressHandler, row) {
			if(!row) {
				var rowNumber = this.tableObject.getIndex();
				row = this.tableObject.getRows()[rowNumber - 1];
			}
			var columnInput = row.cells[columnNumber].getElementsByTagName("input")[0];
			if(keyPressHandler) {
				columnInput.onkeydown = keyPressHandler;
			}
			cacheArray.push(columnInput);
			return cacheArray;
		}
			
		/**
		 * Trims leading spaces from the text passed. Edit this function to change the 
		 * patterns in the displayed text
		 * @param {Object} str
		 */
		var _trim = function (str) {
			var trimExp = new RegExp((this.includeLeadingSpaces ? "" : "\\s*") + "(.*?)" + (this.includeTrailingSpaces ? "" : "\\s*"));
			return str.replace(trimExp, "$1");
		}
		
		/**
		 * Sets the keyup and keydown events for an input field if the menu is just for a single field and not a grid
		 */
		var _setSingleFieldKeyEvents = function () {
			this.fillElmt = window[this.inputFldId];
			this.fillElmt.onkeydown = this.keyEventHandler;
		}
		
		/**
		 * Sets the keyup and keydown events for the column of a grid
		 */
		var _setGridKeyEvents = function() {
			this.columnsArray = this._cacheFullColumns(this.columnNumber, this.keyEventHandler);
		}
		
		/**
		 * Sets the keyup and keydown events for the input field
		 */
		var _setKeyEvents = function () {
			this.keyEventHandler = bindEvent(this._keyEvent, this);
			this[this.isGrid ? "_setGridKeyEvents" : "_setSingleFieldKeyEvents"]();
		}
		
		/**
		 * Handler for processing the key press events in the input field. The event object is passed by the browser.
		 * @param {Object} eventObject
		 */
		var _keyEvent = function (eventObject) {		
			eventObject && cordys.stopPropagation(eventObject);
			var controlKeyMap = {
				"40" : true,
				"13" : true,
				"38" : true,
				"9" : true
			};
		    var eventType = eventObject.type;
		    var charCode = eventObject.which ? eventObject.which : eventObject.keyCode;
		    console.log("key : " + charCode);
			this.isGrid && this._setInputElement();
			var searchText = this.fillElmt.value;
			if(charCode == "13" && (!AutoComplete.isMenuVisible || (AutoComplete.isMenuVisible && searchText != this.previousSearchText))) {
				console.log("menu not visible")
				this.previousSearchText = searchText;
				this._triggerSearchProcess(searchText);
				eventObject.preventDefault();
			} else if (AutoComplete.isMenuVisible && controlKeyMap[charCode]) {
				console.log("menu visible")
				this._menuNavigate(charCode);				
			}
		}


		/**
		 * To set the input element in case of grids
		 */
		var _setInputElement = function () {
			this.fillElmt = this.columnsArray[this.tableObject.getIndex()];
		}
		
		var _menuNavigate = function (charCode) {
		    if(charCode == "40") {
		        this.currentMenuItem.nextSibling && this._changeMenuEntry(this.currentMenuItem.nextSibling, "down");
		    } else if(charCode == "38") {
		        this.currentMenuItem.previousSibling && this._changeMenuEntry(this.currentMenuItem.previousSibling, "up");
		    } else {
		        this._selectionHandler();
		    }
		    this._setScrolls();
		}
	
		/**
		 * Creates a new cache entry for the search text provided
		 * @param {Object} searchText - the cache key with which the cache entry can be retrieved
		 */
		var _newCacheObject = function (searchText) {
			var currentCache = this.currentCacheObj = this.cacheObj[this._getCurrentCacheKey(searchText)] = {};
			currentCache.dataCache = {};
			AutoComplete.eventCaptureLayer.onclick = bindEvent(this.clearMenu, this);
			var menuNode = currentCache.entryNode = document.createElement("div");
			menuNode.className = "menu-div";
			menuNode.style.height = this.menuHeight;
			menuNode.style.width = this.menuWidth;
			menuNode.style.left = this.menuLeft;
			menuNode.style.zIndex = "10001";
		}
		
		/**
		 * To calculate the location of the menu from the top based on the position of the currently selected row
		 */
		var _calculateMenuTop = function () {
			var rowNumber = this.tableObject.getIndex();
			var headerHeight = this.tableObject.tHead.offsetHeight;
			var scrollTop = this.tableObject.parentNode.scrollTop;
			var currentRowTop = this.tableObject.rows[rowNumber + 1].offsetTop;
			var firstRowBottom = Number(this.menuTop.replace("px", ""));
			return (currentRowTop + firstRowBottom - headerHeight - scrollTop) + "px";			
		}
		
		/**
		 * Returns the current cache key formed from the search text and all the webservice input element values
		 * @param {Object} searchText
		 */
		var _getCurrentCacheKey = function (searchText) {
			var key = "";
			var arr = this.wsInputObj;
			for(var i =0; i < arr.length; i++) {
				if(this.isGrid && arr[i].isColumn) {
					var rowNumber = this.tableObject.getIndex(); 
					key += arr[i].element[rowNumber].value;
				} else {
					if(!this.inputFldId || (this.inputFldId && (this.inputFldId != arr[i].fldId))) {
						key += arr[i].element.value;									
					}
				}
			}
			return (key + searchText);
		}
		
		/**
		 * Retrieves an entry from the search cache when provided with the search text
		 * @param {Object} searchText
		 */
		var _getCachedEntry = function (searchText) {
			var currentCacheKey = this._getCurrentCacheKey(searchText);
			var entry = this.cacheObj[currentCacheKey];
			return (entry ? entry : false);
		}
		
		/**
		 * Returns true if cached
		 * @param {Object} searchText
		 */
		var _isCached = function (searchText) {
			return !!this._getCachedEntry(searchText);
		}
		
		/**
		 * Handler that handles the response of the search webservice. Checks whether the ws has to be fired in single 
		 * fire mode and calls parse function accordingly
		 */
		var _searchResponse = function (response, searchText) {
			//console.log("searchText in _searchResponse : " + searchText);
			//console.log("response recieved for : " + eventObject.userArguments.searchText);
			this.response = response;
			this.wsFired = this.singleFire;
			this._parseResponse(response/*this.response*/, searchText);//eventObject.userArguments.searchText);
		}
		
		/**
		 * Parses and caches the search response
		 * @param {Object} result - webservice response
		 */
		var _parseResponse = function (result, searchText) {
			//console.log("searchText in _parseResponse : " + searchText);
			//console.log("Reponse for " + searchText + " : \n" + cordys.getXML(result) + "\n\n\n");
			this._newCacheObject(searchText);
			searchText = this._trim(searchText);		
			var searchTextRegexp = new RegExp(searchText,"i");
			var dataNodes = {};
			result = this._getRearrangedResult(result);
			for(var i = 0; i < this.entryConfigArray.length; i++) {
				var currentTagName = this.entryConfigArray[i].tagName;
				dataNodes[currentTagName] = cordys.selectXMLNodes(result,".//*[local-name()='" + currentTagName + "']");
			}
			var numEntries = dataNodes[currentTagName].length;
			var dataCacheEntry = {};
		    for(index = 0; index < numEntries; index++) {
				var currentEntryDiv = this._createEntryDiv();
				var innerHTML = "";
				for(i = 0; i < this.entryConfigArray.length; i++) {
					var currentTagObj = this.entryConfigArray[i];
					currentTagName = currentTagObj.tagName;
					var currentTagValue = cordys.getTextContent(dataNodes[currentTagName][index]);
					if(currentTagObj["isDisplayValue"])
						innerHTML += (currentTagValue + ", ");
					dataCacheEntry[currentTagName] = currentTagValue;
				}
				if (searchTextRegexp.test(innerHTML)) {
					currentEntryDiv.innerHTML = formatHTML(innerHTML, searchText);
					this.currentCacheObj.entryNode.appendChild(currentEntryDiv);
					this._pushToDataCache(dataCacheEntry, this._getText(currentEntryDiv));
				}
		    }
			this._fillFromCache(searchText);
		}
		
		/**
		 * To rearrange the list of tuples in the order of ascending length of menu entry text
		 */
		var _getRearrangedResult = function (result) {
			var reResult = cordys.loadXMLDocument("<result></result>");
			var resultNode = cordys.selectXMLNode(reResult, ".//*[local-name() = 'result']");
			var tuples = cordys.selectXMLNodes(result, ".//*[local-name() = 'tuple']");
			var tuplesArr = _copyObject(tuples, []);
			tuplesArr.sort((function (that) {
				var tagArr = []
				for(i = 0; i < that.entryConfigArray.length; i++) {
					var currentTagObj = that.entryConfigArray[i];
					if(currentTagObj["includeInSort"]) {
						tagArr.push(currentTagObj["tagName"]);
					}
				}
				return function(node1, node2) {
					var str1 = "", str2 = "";
					for(var j = 0; j < tagArr.length; j++) {
						str1 += cordys.getTextContent(cordys.selectXMLNode(node1, ".//*[local-name() = '" + tagArr[j] + "']"));
						str2 += cordys.getTextContent(cordys.selectXMLNode(node2, ".//*[local-name() = '" + tagArr[j] + "']"));
					}
					return str1.length - str2.length;
				}
			})(this));
			for(var i = 0; i < tuplesArr.length; i++) {
				resultNode.appendChild(tuplesArr[i]);
			}
			return reResult;
		}
		
		/**
		 * To push the dataCacheEntry to the dataCache
		 * @param {Object} src - source object
		 * @param {Object} dest - destination object
		 */
		var _pushToDataCache = function (dataCacheEntry, entryKey) {
			var dest = this.currentCacheObj.dataCache[entryKey] = {};
			_copyObject(dataCacheEntry, dest);
		}
		
		/**
		 * To copy properties from source object to destination object
		 * @param {Object} source
		 * @param {Object} destination
		 */
		var _copyObject = function (source, destination) {
			for(var i in source) {
		    	if(source.hasOwnProperty(i)) {
		        	destination[i] = source[i];
		        }
	        }
			return destination;	
		}	
	
		/**
		 * Setting the scrolls for the menu
		 */
		var _setScrolls = function () {
		    try {
		        var containerNode = AutoComplete.labelElmt.childNodes[0];
		        var menuHeight = containerNode.offsetHeight;
		        var totalMenuHeight = menuHeight + containerNode.scrollTop;
		        var itemTopHeight = this.currentMenuItem.offsetTop;
		        var itemHeight = this.currentMenuItem.offsetHeight;	    
		        if(totalMenuHeight <= (itemTopHeight + itemHeight))
		            containerNode.scrollTop += menuHeight;
		        else if((totalMenuHeight - itemTopHeight) > menuHeight)
		            containerNode.scrollTop -= menuHeight;
		    } catch (exp) {
		    }
		}
		/**
		 * Create a div representing a menu entry div
		 */
		var _createEntryDiv = function () {
			var entryDiv = document.createElement("div");
			entryDiv.className = "inactive-menu-entry";
			entryDiv.onmouseover = bindEvent(this._changeMenuEntry, this);
			entryDiv.onclick = bindEvent(this._selectionHandler, this);
			return entryDiv;
		}
		
		/**
		 * Called onclick or on enter key press over an entry
		 */
		var _selectionHandler = function (eventObject) {
			var divElmt = this.currentMenuItem;
			var currentDataObject = this.currentCacheObj.dataCache[this._getText(divElmt)];
			this.fillElmt.setValue("");
			this.fillElmt.setValue(currentDataObject[this.fieldFillValue]);
			this.clearMenu(eventObject);
			this._triggerSelectionEvent(divElmt);			
		}
		
		/**
		 * To trigger the user selection event
		 * @param {Object} eventObject - div element corresponding to the entry
		 */
		var _triggerSelectionEvent = function (divElmt) {
			if(!this.selectionHandler) {
				return;
			}
			var eventObj = _copyObject(this.currentCacheObj.dataCache[this._getText(divElmt)], {});
			eventObj.HTMLElement = divElmt;
			eventObj.sourceFieldId = this.inputFldId;
			this.selectionHandler(eventObj);
		}
		
		/**
		 * Fills the menu from the cache if the data is already available there else triggers the webservice and
		 * then fills the menu
		 */
		var _triggerSearchProcess = function (searchText) { 
			//console.log("searchText in _triggerSearchProcess : " + searchText);
		    if(searchText == "") {				
		        this.clearMenu();					
		    } else if(this._isCached(searchText)) {				
				//console.log("filling from cache for : " + searchText);
		        this._fillFromCache(searchText);
		    } else if(this.singleFire && this.wsFired) {				
				this._parseResponse(this.response);
			} else {
		    	this._fireSearch(searchText);				
		    }
		}

		/**
		 * Fill the menu by taking the menuNode from the cache. The cache is keyed based on the search text.
		 * @param {Object} searchText - Key to the cache
		 */
		var _fillFromCache = function (searchText) {			
			if(AutoComplete.eventCaptureLayer.childNodes.length > 0) {
				this.clearMenu();
			}
			this.currentCacheObj = this._getCachedEntry(searchText);
			var numEntries = this.currentCacheObj.entryNode.childNodes.length;
			if (numEntries > 0) {
				this.currentCacheObj.entryNode.style.top = this.isGrid ? this._calculateMenuTop() : this.menuTop;
				if(this.fillElmt.value == searchText) {
					AutoComplete.eventCaptureLayer.appendChild(this.currentCacheObj.entryNode);
					this.currentMenuItem = this.currentCacheObj.entryNode.childNodes[0];
					this._changeMenuEntry(this.currentMenuItem);
					setLayerVisibility(true);
				}
			}
		}
		
		/**
		 * Called when the user navigates using the keyboard, onmouseover an entry etc. To change the selection of the
		 * entry to the divElmt that is passed to it
		 * @param {Object} divElmt - The entry div that is to be made in focus now
		 */
		var _changeMenuEntry = function (eventObject, direction) {
		    try {
				var divElmt = eventObject.currentTarget || eventObject;
		        this.currentMenuItem.className = 'inactive-menu-entry';
		        divElmt.className = 'active-menu-entry';
		        this.currentMenuItem = divElmt;
				scrollAdjust(divElmt, direction);
		    } catch (ex) {
		    }
		}
		
		/**
		 * Fire the search webservice that should be fired to get the data corresponding to the text that the user
		 * is typing. The webservice is fired from the model that has been set while creating the AutoComplete
		 * @param {Object} searchText - The input to the webservice which will be the text for which the search has
		 * to be conducted
		 */
		var _fireSearch = function (searchText) {
		     req= this.wsModel.getMethodRequest();
			 this._fillInput(req);
			 var bdiIndex = getUnusedBdi(); 
			 AutoComplete.bdiDiv[bdiIndex].onresponse = (function(that, searchText, bdiIndex){
			 	return function(eventObject) {
					AutoComplete.bdiDiv[bdiIndex].inUse = false;
					that._searchResponse(eventObject.response, searchText)
				}
			 })(this, searchText, bdiIndex);
			 //bindEvent(this._searchResponse, this, {searchText : searchText});
		     AutoComplete.bdiDiv[bdiIndex].clear();
		     AutoComplete.bdiDiv[bdiIndex].request = req;
			 AutoComplete.bdiDiv[bdiIndex].inUse = true;
			 AutoComplete.bdiDiv[bdiIndex].reset();
			 console.log("using bdi '" + bdiIndex + "' for searchText '" + searchText + "'");
			//console.log("request sent for : " + searchText);
		}	
		
		/**
		 * Function to fill the input in the request
		 * @param {Object} request
		 */
		var _fillInput = function (request) {
			for(var i = 0; i < this.wsInputObj.length; i++) {
				var currentInputObj = this.wsInputObj[i];
				if(this.isGrid && currentInputObj.isColumn) {
					var rowNumber = this.tableObject.getIndex();
					currentValue = this._trim(currentInputObj.element[rowNumber].value);
				} else {
					var currentValue = this._trim(currentInputObj.element.value);
				}
				cordys.setTextContent(cordys.selectXMLNode(request, ".//*[local-name() = '" + currentInputObj.tagName + "']"), currentValue);
			}
			return request;
		}
		
		/**
		 * To return the text content inside a div element
		 * @param {Object} divElmt
		 */
		var _getText = function (divElmt) {
			return divElmt.innerText || divElmt.textContent;
		}
		
		/**
		 * Clear the menu and wipe off all the entries
		 */
		var clearMenu = function (eventObject) {
			eventObject && cordys.stopPropagation(eventObject);
			if (this.currentMenuItem) {
				this.currentMenuItem.className = 'inactive-menu-entry';
			}
			AutoComplete.eventCaptureLayer.innerHTML = "";
			setLayerVisibility(false);
		}
		
		/**
		 * To set the selection handler
		 */
		var setSelectionHandler = function (handler) {
			this.selectionHandler = handler;
		}
		
		/**
		 * To handle the insert event in the grid. The newly created row has to be cached in the columnsArray as well as
		 * to the input objects
		 */
		var _insertHandler = function () {
			this._cacheSingleColumn(this.columnNumber, this.columnsArray, this.keyEventHandler);
			for( var i = 0; i < this.wsInputObj.length; i++) {
				var currentInputObj = this.wsInputObj[i];
				if(currentInputObj.isColumn) {
					if(!currentInputObj.element) {
						currentInputObj.element = [null];
					}
					this._cacheSingleColumn(currentInputObj.fldId, currentInputObj.element);
				}
			}
		}
		
		/**
		 * Handler to deal with delete and refreh events of the grid
		 */
		var _deleteAndRefreshHandler = function () {
			this._cacheInputElements();
			this._setGridKeyEvents();
		}
		
		/**
		 * To clear the cache of stored requests
		 */
		var clearCache = function() {
			this.cacheObj = undefined;
		}
		
		return {
			setSelectionHandler : setSelectionHandler,
			clearMenu : clearMenu,
			clearCache : clearCache,
			_searchResponse : _searchResponse,
			_changeMenuEntry : _changeMenuEntry,
			_selectionHandler : _selectionHandler,
			_keyEvent : _keyEvent,
			_setKeyEvents : _setKeyEvents,
			_fireSearch : _fireSearch,
			_fillFromCache : _fillFromCache,
			_triggerSearchProcess : _triggerSearchProcess,
			_triggerSelectionEvent : _triggerSelectionEvent,
			_createEntryDiv : _createEntryDiv,
			_setScrolls : _setScrolls,
			_newCacheObject : _newCacheObject,
			_menuNavigate : _menuNavigate,
			_pushToDataCache : _pushToDataCache,
			_cacheInputElements : _cacheInputElements,
			_fillInput : _fillInput,
			_parseResponse : _parseResponse,
			_getCurrentCacheKey : _getCurrentCacheKey,
			_getCachedEntry : _getCachedEntry,
			_isCached : _isCached,
			_trim : _trim,
			_getText : _getText,
			_setInputElement : _setInputElement,
			_setSingleFieldKeyEvents : _setSingleFieldKeyEvents,
			_setGridKeyEvents : _setGridKeyEvents,
			_cacheFullColumns : _cacheFullColumns,
			_insertHandler : _insertHandler,
			_deleteAndRefreshHandler : _deleteAndRefreshHandler,
			_cacheSingleColumn : _cacheSingleColumn,
			_calculateMenuTop : _calculateMenuTop,
			_getRearrangedResult : _getRearrangedResult
		}
	})();
	
	//Controls the number of BDIs created
	var bdiCount = 10;
	
	/**
	 * Adds some CSS classes to the current document to be used by the menu div
	 */
	function setStyle() {
	    var style='.menu-div{overflow:auto;overflow-y:auto;border:solid 1px #96CAEA;color:black;background-color:#C4EBF6;position:absolute;}\
	               .menu-div div{margin-left:1px;margin-right:1px;}\
	               .active-menu-entry{background-color:#FFF1B7;color:black;}\
	               .inactive-menu-entry{background-color:C4EBF6;color:black;}';
	    var styleElmt=document.createElement("style");
	    styleElmt.setAttribute("type", "text/css");
	    if(styleElmt.styleSheet)    //for IE
	        styleElmt.styleSheet.cssText=style;
	    else                              //the world
	        styleElmt.innerHTML=style;
	    document.getElementsByTagName("head")[0].appendChild(styleElmt);
	}
	
	/**
	 * Adds the BOP4 BDI library to the document for firing webservices
	 */
	function setupBusDataIsland() {
		AutoComplete.bdiDiv = [];
		for(var i = 0; i < bdiCount; i++) {
			AutoComplete.bdiDiv[i] = document.createElement("div");
			document.body.appendChild(AutoComplete.bdiDiv[i]);
			window.application.addType(AutoComplete.bdiDiv[i], "wcp.library.data.BusDataIsland");
			AutoComplete.bdiDiv[i].async= true;
			AutoComplete.bdiDiv[i].id='bdiId' + i;
		}
	}
	
	function getUnusedBdi() {
		for(var i = 0; i < bdiCount; i++) {
			if(!AutoComplete.bdiDiv[i].inUse) {
				return i;
			}
		}
	}
	
	/**
	 * To create an event capture layer for the menu
	 */
	function setupEventCaptureLayer() {
		var element = AutoComplete.eventCaptureLayer = document.createElement("div");
		element.style.width = "100%";
		element.style.height = "100%";
		element.style.zIndex = "10000";
		element.style.position = "absolute";
		element.style.top = "0px";
		element.style.display = "none";
		element.id = "eventCaptureLayer";
	
	    var desktopDocument = top.document.getElementById("desktop").contentWindow.document;
		var dialogRootFrame = desktopDocument.getElementById("ufo_dialog_CordysRoot");
		if(!dialogRootFrame) {
		    var breadCrumDocument = desktopDocument.getElementById("breadcrum").contentWindow.document;
		    var mainDiv = breadCrumDocument.getElementById("main");
		    var lastDivIndex = mainDiv.childNodes.length;
			dialogRootFrame = mainDiv.childNodes[lastDivIndex - 1].childNodes[0];		
		}
		var xbody = dialogRootFrame.contentWindow.document.getElementById("xbody_group");
		xbody.appendChild(element);	
	}

	/**
	 * Initialization for the auto complete menu. This function has to be called first before creating any menus. 
	 * A label has to be added in the xform and its field ID has to be passed to this function initially.
	 * @param {Object} labelId - field id of the dummy label that acts as a container for all the menu divs 
	 */
	(function init() {
		setStyle();
		setupBusDataIsland();
		setupEventCaptureLayer();
	})();

	/**
	 * To set the visibility of the event capture layer. Sets the isMenuVisible property
	 * to true if the menu is visible.
	 * @param {Object} isVisible - visible if true
	 */
	function setLayerVisibility(isVisible) {
		AutoComplete.eventCaptureLayer.style.display = isVisible ? "block" : "none";
		AutoComplete.isMenuVisible = isVisible;
	}

	/**
	 * Returns a function variable that calls the eventHandler function with the object passed as the this object. Used
	 * in setting the events like keyup, keydown and WS Response handlers so that the AutoComplete object is 
	 * available in the handlers
	 * @param {Object} eventHandler - handler function to be called
	 * @param {Object} thisObj - object to be used as the this object for this handler
	 */
	function bindEvent(eventHandler, thisObj, userArguments) {
		return function (eventObject) {
			eventObject = eventObject || window.event || {};
			userArguments && (eventObject.userArguments = userArguments);
			eventHandler.call(thisObj, eventObject || window.event);
		}
	}
	
	/**
	 * Appends "px" to the end of a string if it is not already added.
	 * @param {Object} str
	 */
	function addPx(str) {
		return (/px$/.test(str)) ? str : (str + "px");			
	}
	
	/**
	 * To adjust the scroll in the list while the user scrolls down using the keyboard
	 * @param {Object} entry - The current entry that the user has selected
	 * @param {Object} direction - "up" if the user scrolled down, "down" elsewise
	 */
	function scrollAdjust(entry, direction) {
		var container = entry.parentNode;
		if(!direction) {
			return;
		} else if(direction == "up") {
			var scrollTop = entry.offsetTop;
		} else {
			scrollTop = entry.offsetTop + entry.offsetHeight - container.offsetHeight;
		}
		container.scrollTop = scrollTop;			
	}
	
	function formatHTML(htmlString, searchText) {
		//Removing trailing comma. Replacing double spaces with single space
		htmlString = getBold(htmlString, searchText).replace(/\,\s$/,"").replace(/\s\s/g, " ");
		//Removing leading and trailing spaces
		htmlString = htmlString.replace(/^\s+|\s+$/g, "");
		return htmlString;	
	}

	/**
	 * To make the searched string bold in the inner HTML
	 */
	var getBold = function (innerHTML, searchText) {
	    var regex = new RegExp("(.*?)(" + searchText.replace(/^\s+|\s+$/g, "") + ")(.*)", "i");
	    var replacedString = innerHTML.replace(regex, "$1<b>$2</b>$3");
	    return replacedString;
	}
	
	return AutoComplete;	
})();
scriptCallback && scriptCallback("Auto Complete");